package org.mookt.quantity;

import java.math.BigDecimal;
import java.math.RoundingMode;

import org.mookt.unit.Unit;

/**
 * This interface imposes a quantifiable behaviour on objects that implements
 * it. This behaviour is encountered when one represents dimensioned values with
 * both amounts and units, e.g. 10s, 15m/s or $100.
 * 
 * Any classes implementing this interface should impose total ordering at least
 * by its amount, but the actual implementation is open to be defined by the
 * implementator.
 * 
 * @author Ricardo Engel
 * 
 * @param <T> the class implementing this interface.
 */
public interface Quantifiable<T> extends Comparable<T> {

    public BigDecimal getAmount();

    public Unit getUnit();

    /**
     * Imposes ordering on two different objects based on the values on each
     * {@code Quantifiable} object.
     * 
     * @param o the {@code Quantifiable} instance to compare this one to.
     * 
     * @see Comparable#compareTo(Object)
     * @throws NullPointerException if {@code o} is null.
     * @throws IllegalArgumentException if the objects being compared don't have
     *         the same unit.
     */
    public int compareTo(final T o);

    /**
     * Checks if the amount is zero.
     * 
     * @return {@code true} if the amount is zero, {@code false} otherwise.
     */
    public boolean isZero();

    /**
     * Checks if the amount is (strictly) positive.
     * 
     * @return {@code true} if the amount is positive, {@code false} otherwise.
     */
    public boolean isPositive();

    /**
     * Checks if the amount is (strictly) negative.
     * 
     * @return {@code true} if the amount is negative, {@code false} otherwise.
     */
    public boolean isNegative();

    /**
     * Checks if the amount is greater than the {@code other} amount.
     * 
     * <p>
     * Scale is ignored in the comparison. The compared values must be in the
     * same unit.
     * </p>
     * 
     * @param other the {@code Quantifiable} instance to compare this one to.
     * 
     * @return {@code true} if the amount is greater, {@code false} otherwise.
     * @throws NullPointerException if {@code o} is null.
     * @throws IllegalArgumentException if the units are different.
     */
    public boolean isGreaterThan(final T other);

    /**
     * Checks if the amount is smaller than the {@code other} amount.
     * 
     * <p>
     * Scale is ignored in the comparison. The compared values must be in the
     * same unit.
     * </p>
     * 
     * @param other the {@code Quantifiable} instance to compare this one to.
     * 
     * @return {@code true} if the amount is smaller, {@code false} otherwise.
     * @throws NullPointerException if {@code o} is null.
     * @throws IllegalArgumentException if the units are different.
     */
    public boolean isSmallerThan(final T other);

    /**
     * Returns a copy of this {@code Quantifiable} with the {@code amount}
     * added.
     * 
     * <p>
     * This adds the specified amount to this {@code Quantifiable}'s as a new
     * object. No rounding is performed on the amount to be added.
     * </p>
     * 
     * <p>
     * This instance is unaffected by this method.
     * </p>
     * 
     * @param amount the amount to be added, not null.
     * @return the new instance with the argument amount added, never null.
     * @throws NullPointerException if the {@code amount} argument is null.
     */
    public T plus(final BigDecimal amount);

    /**
     * Returns a copy of this {@code Quantifiable} with the specified
     * {@code amount} added.
     * 
     * <p>
     * This adds the specified amount to this {@code Quantifiable}'s as a new
     * object. No rounding is performed on the amount to be added.
     * </p>
     * 
     * <p>
     * This instance is unaffected by this method.
     * </p>
     * 
     * @param amount the amount to be added.
     * @return the new instance with the argument amount added, never null.
     */
    public T plus(final double amount);

    /**
     * Returns a copy of this {@code Quantifiable} with the {@code other}'s
     * amount added.
     * 
     * <p>
     * This adds the specified {@code other}'s amount to this
     * {@code Quantifiable}'s as a new object. No rounding is performed on the
     * amount to be added.
     * </p>
     * 
     * <p>
     * This instance is unaffected by this method.
     * </p>
     * 
     * @param other the {@code Quantifiable} with the amount to be added, not
     *        null.
     * @return the new instance with the {@code other}'s amount added, never
     *         null.
     * @throws NullPointerException if the {@code other} argument is null.
     * @throws IllegalArgumentException if the units being added are different.
     */
    public T plus(final T other);

    /**
     * Returns a copy of this {@code Quantifiable} with the specified
     * {@code amount} subtracted.
     * 
     * <p>
     * This subtracts the specified {@code amount} of this {@code Quantifiable}
     * 's as a new object. No rounding is performed on the amount to be
     * subtracted.
     * </p>
     * 
     * <p>
     * This instance is unaffected by this method.
     * </p>
     * 
     * @param amount the amount to be subtracted, not null.
     * @return the new instance with the argument amount added, never null.
     * @throws NullPointerException if the {@code amount} argument is null.
     */
    public T minus(final BigDecimal amount);

    /**
     * Returns a copy of this {@code Quantifiable} with the specified
     * {@code amount} subtracted.
     * 
     * <p>
     * This subtracts the specified {@code amount} of this {@code Quantifiable}
     * 's as a new object. No rounding is performed on the amount to be
     * subtracted.
     * </p>
     * 
     * <p>
     * This instance is unaffected by this method.
     * </p>
     * 
     * @param amount the amount to be subtracted.
     * @return the new instance with the argument amount added, never null.
     */
    public T minus(final double amount);

    /**
     * Returns a copy of this {@code Quantifiable} with the {@code other}'s
     * amount subtracted.
     * 
     * <p>
     * This subtracts the specified {@code other}'s amount of this
     * {@code Quantifiable}'s as a new object. No rounding is performed on the
     * amount to be subtracted.
     * </p>
     * 
     * <p>
     * This instance is unaffected by this method.
     * </p>
     * 
     * @param other the {@code Quantifiable} to be subtracted, not null.
     * @return the new instance with the {@code other}'s amount subtracted,
     *         never null.
     * @throws NullPointerException if the {@code other} argument is null.
     * @throws IllegalArgumentException if the units being subtracted are
     *         different.
     */
    public T minus(final T other);

    /**
     * Returns the additive inverse of this object, such that
     * {@code this.plus(this.opposite).isZero() == true}.
     * 
     * @return the new {@code Quantifiable} with opposite amount but same unit,
     *         never null.
     */
    public T opposite();

    /**
     * Returns a copy of this {@code Quantifiable} with its amount multiplied by
     * the specified {@code amount}.
     * 
     * <p>
     * No precision is lost in the result. The result has a scale equal to the
     * sum of the two scales. For example, 'USD 1.13' multiplied by '2.5' gives
     * 'USD 2.825'.
     * </p>
     * 
     * <p>
     * This instance is immutable and unaffected by this method.
     * </p>
     * 
     * @param amount the {@code amount} to multiply by, not null.
     * @return the new multiplied instance, never null.
     * @throws NullPointerException if {@code amount} is null.
     */
    public T times(final BigDecimal amount);

    /**
     * Returns a copy of this {@code Quantifiable} with its amount multiplied by
     * the specified {@code amount}.
     * 
     * <p>
     * No precision is lost in the result. The result has a scale equal to the
     * sum of the two scales. For example, 'USD 1.13' multiplied by '2.5' gives
     * 'USD 2.825'.
     * </p>
     * 
     * <p>
     * This instance is immutable and unaffected by this method.
     * </p>
     * 
     * @param amount the {@code amount} to multiply by.
     * @return the new multiplied instance, never null.
     */
    public T times(final double amount);

    /**
     * Returns a copy of this {@code Quantifiable} with its amount divided by
     * the specified {@code amount}.
     * 
     * <p>
     * No precision is lost in the result. The result has a scale equal to the
     * difference of the two scales. For example, '1 s' divided by '32' gives
     * '0.03125 s'.
     * </p>
     * 
     * <p>
     * This instance is immutable and unaffected by this method.
     * </p>
     * 
     * @param amount the {@code amount} to divide by, not null.
     * @return the new divided instance, never null.
     * @throws NullPointerException if {@code amount} is null.
     * @throws ArithmeticException if dividing by zero.
     * @throws ArithmeticException if the exact quotient does not have a
     *         terminating decimal expansion.
     */
    public T dividedBy(final BigDecimal amount);

    /**
     * Returns a copy of this {@code Quantifiable} divided by the specified
     * {@code amount}.
     * 
     * <p>
     * No precision is lost in the result. The result has a scale equal to the
     * difference of the two scales. For example, '1 s' divided by '32' gives
     * '0.03125 s'.
     * </p>
     * 
     * <p>
     * This instance is immutable and unaffected by this method.
     * </p>
     * 
     * @param amount the {@code amount} to divide by.
     * @return the new divided instance, never null.
     * @throws ArithmeticException if dividing by zero.
     * @throws ArithmeticException if the exact quotient does not have a
     *         terminating decimal expansion.
     */
    public T dividedBy(final double amount);

    /**
     * Returns a copy of this {@code Quantifiable} with its amount rounded to
     * the specified decimal places.
     * <p>
     * For example, rounding:
     * <ul>
     * <li>'45.23 s' to -1 returns 40.00 or 50.00 depending on the rounding
     * mode.
     * <li>'45.23 s' to 0 returns 45.00 or 46.00 depending on the rounding mode.
     * <li>'45.23 s' to 1 returns 45.20 or 45.30 depending on the rounding mode.
     * <li>'45.23 s' to 2 has no effect (it already has that number of decimal
     * places).
     * <li>'45.23 s' to 3 has no effect (the number of decimal places is not
     * increased).
     * </ul>
     * <p>
     * 
     * @param decimalPlaces the number of decimal places.
     * @param roundingMode the rounding mode to use, not null.
     * @return the new instance with the amount rounded, never null.
     * @throws ArithmeticException if the rounding fails.
     */
    public T round(final int decimalPlaces, final RoundingMode roundingMode);

    /**
     * Returns a copy of this {@code Quantifiable} with its amount to the power
     * of {@code n}, the power is computed exactly, to unlimited precision.
     * 
     * <p>
     * The parameter {@code n} must be in the range 0 through 999999999,
     * inclusive.
     * </p>
     * 
     * <p>
     * If q is a {@code Quantifiable} object with zero amount, q.pow(0) returns
     * a different object with amount one and same unit.
     * </p>
     * 
     * <p>
     * Note that future releases may expand the allowable exponent range of this
     * method.
     * </p>
     * 
     * @param n power to raise this {@code Quantifiable} to.
     * @return A copy of this {@code Quantifiable} to the power of {@code n}.
     * @throws ArithmeticException if {@code n} is out of range.
     */
    public T pow(final int n);

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals(final Object obj);

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#toString()
     */
    public String toString();

}